#include "GameHandler.h"
#include <cmath>
#include "Item/ItemFactory.h"

#define d_Player WORLD_MODULE::Ins()->Players[LOCAL_PLAYER]
#define d_input  Engine::Ins()->input

GameHandler::GameHandler()
	: m_eGameState(STATE_INTRO_MENU)
	, m_iNumPlayers(0)
	, musicOn(false)
	, m_iPlayerIndex(-1)
	, m_bNeedToSendData(true)
	, m_bJoinedMultiplayer(false)
	, m_bWorldInitialized(false)
{}

/**
* This function should not load anything related to the game world. 
* That will happen inside GameHandler::Update() in the STATE_LOAD_GAME section.
*/
void GameHandler::Init()
{
	GUI.initialize();
	AtmoSphere::get().init();
	autoLootWorldBlocks = true;
	GUI.setMessage("Autolooting Active",GUI_Handler::AUTOLOOTINFO);
}

void GameHandler::InitGameWorld()
{
	Factory::get().initAssets();

	if(m_iPlayerIndex == 0 || !m_network.isInitialized())
		WORLD_MODULE::Ins()->InitializeNewExA(KILOMETER, KILOMETER, true);

	else if(m_network.isInitialized())
		if(WORLD_MODULE::Ins()->GetSaveData() != NULL)
			WORLD_MODULE::Ins()->InitializeExisting(WORLD_MODULE::Ins()->GetPath2MainSaveDir(),WORLD_MODULE::Ins()->GetPath2SaveDir(), KILOMETER, KILOMETER);

	m_iNumPlayers++;
	m_bWorldInitialized = true;
}

void GameHandler::InitPreloaded() {
	Factory::get().initAssets();

	if(m_iPlayerIndex == 0 || !m_network.isInitialized())
		WORLD_MODULE::Ins()->InitializeExisting(MAP_FOLDER_NAME, "Preloaded", KILOMETER, KILOMETER);

	else if(m_network.isInitialized())
		if(WORLD_MODULE::Ins()->GetSaveData() != NULL)
			WORLD_MODULE::Ins()->InitializeExisting(WORLD_MODULE::Ins()->GetPath2MainSaveDir(),WORLD_MODULE::Ins()->GetPath2SaveDir(), KILOMETER, KILOMETER);

	m_iNumPlayers++;
	m_bWorldInitialized = true;
}

void GameHandler::ReloadState() {m_eGameState = STATE_GAME_RELOAD_WORLD;}
void GameHandler::ReloadWorld() {WORLD_MODULE::Ins()->Regenerate();}

/* Here's an example of how to make the call to add to the drawlist */
//Engine::Ins()->dlHandler.AddObject();

int GameHandler::Update()
{
	int buttonPressed = NOBUTTON;
	GUI.update(m_eGameState,d_input.mousePos());

	switch(m_eGameState)
	{
	case STATE_NETWORK_CANT_CONNECT:
	case STATE_INTRO_MENU:
		Engine::Ins()->sound.PlayCue("Titlesong"); 
		AtmoSphere::get().update(Vector3D(0,0,0));
	case STATE_MULTI_GAME_SELECT:
		HandleMenuInput();
		AtmoSphere::get().update(Vector3D(0,0,0));
		buttonPressed = GUI.getLastButtonClicked();
		break;
	case STATE_LOAD_MULTI_GAME_SERVER:
		m_network.Init(NETWORK_HOST, NETWORK_DEFAULT_PORT, GUI.getTextByIndex(GUI.IPADDRESS));
		m_eGameState = STATE_LOAD_SINGLE_GAME;
		m_iPlayerIndex = 0;
		break;
	case STATE_LOAD_MULTI_GAME_CLIENT:
		// initialize the network, then wait for server response
		if(!m_network.isInitialized())
		{
			m_network.Init(NETWORK_CLIENT, NETWORK_DEFAULT_PORT, GUI.getTextByIndex(GUI.IPADDRESS));
			m_network.MakeServerRequest(NETWORK_REQUEST_WORLD_SEED_FILES, NULL, 0);
		}
		else
			handleServerMessages();
		if(VerifyWorld())
			m_eGameState = STATE_LOAD_SINGLE_GAME;
		break;
	case STATE_LOAD_PRELOADED_GAME:
		{
			GUI.deactivateChatbox();
			m_eGameState = STATE_GAME;
			InitPreloaded();

			if(m_network.isInitialized())
			{
				WORLD_MODULE::Ins()->Players[LOCAL_PLAYER]->playerIndex = m_iPlayerIndex;
				WORLD_MODULE::Ins()->Players[LOCAL_PLAYER]->Move(m_iPlayerIndex*10.0f, 0.0f, 0.0f);
			}
			GUI.update(m_eGameState,d_input.mousePos());
			Engine::Ins()->sound.StopCue("Titlesong"); 
			Engine::Ins()->sound.PlayCue("Space"); 
		};
		break;
	case STATE_LOAD_SINGLE_GAME:
		{
			GUI.deactivateChatbox();
			m_eGameState = STATE_GAME;
			InitGameWorld();

			if(m_network.isInitialized())
			{
				WORLD_MODULE::Ins()->Players[LOCAL_PLAYER]->playerIndex = m_iPlayerIndex;
				WORLD_MODULE::Ins()->Players[LOCAL_PLAYER]->Move(m_iPlayerIndex*10.0f, 0.0f, 0.0f);
			}
			GUI.update(m_eGameState,d_input.mousePos());
			Engine::Ins()->sound.StopCue("Titlesong"); 

		}
		break;
	case STATE_GAME:
		Factory::get().update();
	case STATE_GAME_CHAT:
	case STATE_GAME_CONTROLS:
	case STATE_GAME_INVENTORY:
	case STATE_GAME_CRAFT:
		Engine::Ins()->sound.PlayCue("Space"); 
		if(musicOn == true) Engine::Ins()->sound.PlayCue("Music"); 
		HandleWorldInput();
		HandleMenuInput();
		GUI.update(m_eGameState,d_input.mousePos());
		buttonPressed = GUI.getLastButtonClicked();	
		WORLD_MODULE::Ins()->Update();
		m_network.Update();
		handleServerMessages();
		break;
	case STATE_GAME_RELOAD_WORLD:
		ReloadWorld();
		m_eGameState = STATE_GAME;
		break;
	}

	if (buttonPressed!= NOBUTTON)
		Engine::Ins()->sound.PlayCue("click");
	switch(buttonPressed)
	{
	case BUTTON_ERROR_CONFIRM:	m_eGameState = STATE_INTRO_MENU;			break;
	case BUTTON_SINGLEPLAYER:	m_eGameState = STATE_LOAD_SINGLE_GAME;		break;
	case BUTTON_PRELOAD:		m_eGameState = STATE_LOAD_PRELOADED_GAME;	break;
	case BUTTON_GAME_PLAY:		m_eGameState = STATE_GAME;					break;
	case BUTTON_MULTIPLAYER:	m_eGameState = STATE_MULTI_GAME_SELECT;		break;	
	case BUTTON_SERVER:			m_eGameState = STATE_LOAD_MULTI_GAME_SERVER;break;
	case BUTTON_CLIENT:			m_eGameState = STATE_LOAD_MULTI_GAME_CLIENT;break;
	case BUTTON_TOGGLE_AUTOLOOT:
		if(!autoLootWorldBlocks)
		{
			autoLootWorldBlocks = true;
			GUI.setMessage("Autolooting Active",GUI_Handler::AUTOLOOTINFO);
		}
		else
		{
			autoLootWorldBlocks = false;
			GUI.setMessage("Autolooting Inactive",GUI_Handler::AUTOLOOTINFO);
		}
	break;
	default:					break;
	}

	return m_eGameState;
}

bool GameHandler::VerifyWorld()
{
	if(m_iPlayerIndex == 0)								// if player is the server, no need to verify
		return true;

	m_network.Update();

	_World* w = WORLD_MODULE::Ins();
	if(w->GetSaveData())								// if the world generation seed has been properly set							
		return true;

	return false;
}

void GameHandler::HandleWorldInput()
{
	switch(m_eGameState)
	{
	case STATE_GAME:
		//Movement Keys
		GUI.setMessage(d_input.getLastKeyPress());
		
		if(d_input.keyHeld(DIK_A))			d_Player->MovementQueue.push(LEFT);
		else if(d_input.keyHeld(DIK_D))		d_Player->MovementQueue.push(RIGHT);
		else								d_Player->MovementQueue.push(NONE);

		//Interaction keys
		if(d_input.keyPressed(DIK_SPACE) || d_input.keyPressed(DIK_W))		d_Player->MovementQueue.push(UP);

		if(d_input.keyPressed(DIK_T))		
		{
			if(Engine::Ins()->game.m_network.isInitialized())
			{
				m_eGameState = STATE_GAME_CHAT;
				GUI.setMessage("",GUI.CHATBOXINPUT);
				d_input.resetKey(DIK_T);
			}

		}
		if(d_input.keyPressed(DIK_M)){
			if(musicOn == false){
				Engine::Ins()->sound.PlayCue("Music"); musicOn = true;}
			else if(musicOn == true){
				Engine::Ins()->sound.StopCue("Music"); musicOn = false;}
		}
		if(d_input.mouseButtonDown(LMB))	d_Player->ActivityQueue.push(SWING);
		if(d_input.mouseButtonHit(RMB))		d_Player->ActivityQueue.push(CREATESPELL);
		if(d_input.mouseButtonDown(RMB))	d_Player->ActivityQueue.push(CHARGE);
		if(d_input.mouseButtonUp(RMB))		d_Player->ActivityQueue.push(CAST);
		if(d_input.keyPressed(DIK_F5))		d_Player->ActivityQueue.push(SAVE);
		if(d_input.keyPressed(DIK_H))		GUI.resetTutorials();
		if(d_input.keyPressed(DIK_ESCAPE))	GUI.stopTutorials();
		if(d_input.keyPressed(DIK_SLASH))	m_eGameState = STATE_GAME_CONTROLS; 
		if(d_input.keyPressed(DIK_C))
		{
			d_Player->ActivityQueue.push(CRAFTCHECK);	//Player has to update their inventory so that the crafTable isn't mistaken
			m_eGameState = STATE_GAME_CRAFT; 
		}
		if(d_input.keyPressed(DIK_I))
		{	
			d_Player->ActivityQueue.push(INVCHECK);
			m_eGameState = STATE_GAME_INVENTORY;
		}
		break;
	case STATE_GAME_CRAFT:
		GUI.setMessage(d_input.getLastKeyPress());
		d_Player->PC_handleGuiMessage(GUI.getPlayerInvMessage());
		if(d_input.keyPressed(DIK_ESCAPE))	m_eGameState = STATE_GAME;
		if(d_input.keyPressed(DIK_I))
		{	
			d_Player->ActivityQueue.push(INVCHECK);
			m_eGameState = STATE_GAME_INVENTORY;
		}
		break;
	case STATE_GAME_INVENTORY:
		GUI.setMessage(d_input.getLastKeyPress());
		d_Player->PC_handleGuiMessage(GUI.getPlayerInvMessage());
		if(d_input.keyPressed(DIK_ESCAPE))	m_eGameState = STATE_GAME;
		if(d_input.keyPressed(DIK_C))
		{
			d_Player->ActivityQueue.push(CRAFTCHECK);	//Player has to update their inventory so that the crafTable isn't mistaken
			m_eGameState = STATE_GAME_CRAFT; 
		}
		if(d_input.keyPressed(DIK_I))
		{	
			d_Player->ActivityQueue.push(INVCHECK);
			m_eGameState = STATE_GAME_INVENTORY;
		}
		break;
	case STATE_GAME_CHAT:
		if(d_input.keyPressed(DIK_ESCAPE)) 	m_eGameState = STATE_GAME;
		break;
	case STATE_GAME_CONTROLS:
		if(d_input.keyPressed(DIK_ESCAPE)) 	m_eGameState = STATE_GAME;
		break;
	}
}
void GameHandler::HandleMenuInput()
{
	switch(m_eGameState)	
	{
	case STATE_GAME_CHAT:			GUI.setMessage(d_input.getLastKeyPress());	break;
	case STATE_MULTI_GAME_SELECT:	GUI.setMessage(d_input.getLastKeyPress());	break;
	}
	if(Engine::Ins()->input.mouseButtonHit(LMB))	
		GUI.checkCollision(m_eGameState,d_input.mousePos(),d_input.keyHeld(DIK_LSHIFT));
}
void GameHandler::Release()
{
	AtmoSphere::get().release();
	GUI.release();
	Factory::get().Release();
	WORLD_MODULE::Ins()->CleanUp();
	m_network.Release();
}
double GameHandler::getAngleToScreen()		//Returns as a degree
{
	POINT mouse = Engine::Ins()->input.mousePos();
	float angle =  (180*std::atan2(((float)g_scrWidth/2-mouse.x),((float)g_scrHeight/2 - mouse.y)))/PI;
	if(angle < 0)	angle = 180 + fabs(180+angle);		//Convert from the strange values atan2 returns
	angle+=90;		//Rotate once so that 0 is 3 o'clock and 180 is 9 o'clock
	if(angle > 360)
		angle -= 360;
	return angle;

}
double GameHandler::getAngleBetweenPoints(Vector3D me, Vector3D them)		//Returns as a degree
{
	//POINT mouse = Engine::Ins()->input.mousePos();
	float angle =  (180*std::atan2(((float)me.x - them.x),((float)me.y - them.y)))/PI;
	if(angle < 0)	angle = 180 + fabs(180+angle);		//Convert from the strange values atan2 returns
	angle+=90;		//Rotate once so that 0 is 3 o'clock and 180 is 9 o'clock
	if(angle >= 360)
		angle -= 360;
	return angle;

}
double GameHandler::distanceToClick()
{
	POINT mouse = Engine::Ins()->input.mousePos();
	float distance = sqrt(pow((float)(mouse.x-g_scrWidth/2),2)+pow((float)(g_scrHeight/2-mouse.y),2));
	//Then divide the distance by a number to find out how many blocks appear in that place
	return 0;
}

void GameHandler::handleServerMessages()
{
	ByteBuffer* in = m_network.getServerMessages();
	if(m_bWorldInitialized && m_bbWorldData.size())
		in->addBytes(m_bbWorldData.getRawList(), m_bbWorldData.size());

	if(!in)
		return;


	if(m_bNeedToSendData &&
		WORLD_MODULE::Ins()->Players[LOCAL_PLAYER] &&
		WORLD_MODULE::Ins()->Players[LOCAL_PLAYER]->playerIndex >= 0)
	{
		int code;
		Entity* player = WORLD_MODULE::Ins()->Players[LOCAL_PLAYER];
		if(m_bJoinedMultiplayer)
			code = NETWORK_GIVE_PLAYER_INFO;
		else
		{
			code = NETWORK_NEW_PLAYER_INFO;
			m_bJoinedMultiplayer = true;
		}

		// send the player
		m_network.MakeServerRequest(code, player, sizeof(Entity*));
		m_bNeedToSendData = false;
	}

	int index = 0;
	while(index < in->size())
	{
		int code = in->getLong(index);
		index += sizeof(long);
		int size = in->getLong(index);
		index+= sizeof(long);

		switch(code)
		{
		case NETWORK_REQUEST_CLIENT_INDEX:
			{
				m_network.setClientIndex(in->getLong(index));
				m_iPlayerIndex = in->getLong(index);
				index += sizeof(long);
				break;
			}
		case NETWORK_CHATBOX_NEW_MESSAGE:
			{
				char* message = new char[size];

				// allocates a string, and byte by byte adds characters to it
				for(int i = 0; i < size; ++i)
					message[i] = in->getByte(index+i);

				// make a copy of message to give to the chatbox so we can delete ours
				GUI.setMessage(message, GUI_Handler::CHATBOXOUTPUT);
				delete message;
				index += size;
				break;
			}
		case NETWORK_NEW_PLAYER_INFO:
		case NETWORK_GIVE_PLAYER_INFO:
		case NETWORK_SEND_ENTITY:
			{
				Entity* e = new Entity;
				ByteBuffer data;
				int dataIndex = 0;
				int  result;

				for(int i = index; i < size+index; ++i)
					data.addByte(in->getByte(i));

				if(!m_bWorldInitialized)
					m_bbWorldData.addBytes(data.getRawList(), data.size());

				e->deserialize(&data, dataIndex);
				e->bDummy = true;

				if(code == NETWORK_NEW_PLAYER_INFO ||
					code == NETWORK_GIVE_PLAYER_INFO)
				{
					result = WORLD_MODULE::Ins()->addNewPlayer(e);

					// if e is an entity we already have, discard it and stop
					if(result == -1)
						delete e;
					else
					{
						m_iNumPlayers = result+1;
						char buffer[64];

						// in case of a new player joining, send this player's information back
						if(code == NETWORK_NEW_PLAYER_INFO)
						{
							sprintf_s(buffer, 64, "Player %i has joined the game.", e->playerIndex+1);
							GUI_Handler::get().setMessage(buffer, GUI_Handler::CHATBOXOUTPUT);
							m_bNeedToSendData = true;

							// send the enemies if this is the server
							if(WORLD_MODULE::Ins()->Players[LOCAL_PLAYER]->playerIndex == 0)
								for(int i = 0; i < WORLD_MODULE::Ins()->getNumEntities(); ++i)
								{
									Entity* e = WORLD_MODULE::Ins()->getEntity(i);
									if(e->playerIndex == -2)
										m_network.MakeServerRequest(NETWORK_SEND_ENTITY, e, sizeof(Entity*));
								}
						}
					}
				}
				else
				{
					WORLD_MODULE::Ins()->addNewEntity(e);
				}
				index += data.size();
				break;
			}
		case NETWORK_UPDATE_ENTITY:
		case NETWORK_UPDATE_ENEMY:
			{
				ByteBuffer data;
				for(int i = index; i < size+index; ++i)
					data.addByte(in->getByte(i));

				if(!m_bWorldInitialized)
					m_bbWorldData.addBytes(data.getRawList(), data.size());

				else
				{
					MoveInfo* m = new MoveInfo;
					int search = -1;
					int dataIndex = 0;

					m->index = data.getShort(dataIndex); dataIndex += sizeof(short);
					if(!data.getByte(dataIndex))
						m->bGravity = false;
					else
						m->bGravity = true;
					dataIndex += sizeof(char);
					m->pos.deserialize(&data, dataIndex);
					m->p.deserialize(&data, dataIndex);

					if(code == NETWORK_UPDATE_ENTITY)
					{
						for(int i = 0; i < m_iNumPlayers; ++i)
						{
							if(WORLD_MODULE::Ins()->Players[i] &&
								WORLD_MODULE::Ins()->Players[i]->playerIndex == m->index)
								search = i;
						}
						if(search >= 0 && search < m_iNumPlayers)
						{	
							WORLD_MODULE::Ins()->Players[search]->CalcMomentumM(m->pos, m->p);
							WORLD_MODULE::Ins()->Players[search]->bGravity = m->bGravity;
						}
					}
					else if(code == NETWORK_UPDATE_ENEMY)
					{
						if(m->index >= 0 && m->index < WORLD_MODULE::Ins()->getNumEntities())
						{
							Entity* e = WORLD_MODULE::Ins()->getEntity(m->index);
							e->CalcMomentumM(m->pos, m->p);
							e->bGravity = m->bGravity;
						}
					}

					delete m;
				}
				index += size;
				break;
			}
		case NETWORK_REQUEST_WORLD_SEED_FILES:
			{
				char** ret = NULL;
				// normally size represents number of bytes, but I'm using it differently here to represent the number of files being sent
				int numFiles = size;

				if(!numFiles)
				{
					// take client back to menu, since server is full
					m_network.Release();
					m_eGameState = STATE_NETWORK_CANT_CONNECT;
					GUI_Handler::get().changeTextBoxString(
						GUI_Handler::get().errorMessageIndex, "Server is full");
				}
				else
				{
					int MapW = in->getLong(index); index += sizeof(long);
					int MapH = in->getLong(index); index += sizeof(long);


					ret = new char*[MapW*MapH];
					for(int i = 0; i < MapW*MapH; ++i)
					{
						char buffer[8];
						buffer[0] = '\0';
						index++;
						// string size is stored as the first number in the string, so retrieve that
						for(int j = 0, ii = index; in->getByte(ii) != ' '; ++j, ii++)
						{
							buffer[j+1] = buffer[j];
							buffer[j] = in->getByte(ii);
						}
						index--;

						// now allocate enough memory based on that number
						int arraySize = atoi(buffer)+1;
						ret[i] = new char[arraySize];

						// copy over the data until we hit a null terminator
						for(int j = 0; in->getByte(index); index++, j++)
							ret[i][j] = in->getByte(index);

						// add on the null terminator at the end, then increment past it
						ret[i][arraySize-1] = '\0';
						char* temp = ret[i];
						index++;
					}

					WORLD_MODULE::Ins()->SetSaveData(ret, MapH*MapW);
				}
				break;
			}
		case NETWORK_UPDATE_WORLD_BLOCK:
			{
				int i;
				ByteBuffer data;
				for(i = index; i < index+size; ++i)
					data.addByte(in->getByte(i));

				if(!m_bWorldInitialized)
					m_bbWorldData.addBytes(data.getRawList(), data.size());
				else
				{
					WorldChange* wc = new WorldChange;

					i = 0;
					wc->Data = data.getLong (i); i += sizeof(long);
					wc->x	 = data.getShort(i); i += sizeof(short);
					wc->y	 = data.getShort(i); i += sizeof(short); 

					WORLD_MODULE::Ins()->AddChange(wc);
				}

				index += data.size();
				break;

			}
		case NETWORK_CLIENT_DISCONNECT:
			{
				short clientIndex = in->getShort(index); index += sizeof(short);
				if(clientIndex >= 0 && clientIndex < MAX_PLAYERS)
					WORLD_MODULE::Ins()->removeEntity(clientIndex);
				break;
			}
		case NETWORK_ERR_HOST_DISCONNECTED:
			{
				m_eGameState = STATE_NETWORK_CANT_CONNECT;
				m_network.Release();
				break;
			}
		case NETWORK_UPDATE_PLAYER_EQUIPS:
			{
				ByteBuffer* data = new ByteBuffer;
				for(int i = index; i < index+size; ++i)
					data->addByte(in->getByte(i));
				if(!m_bWorldInitialized)
					m_bbWorldData.addBytes(data->getRawList(), data->size());
				else
				{
					Item_Equippable* item = NULL;
					int counter = 0;
					int shortSize = sizeof(short);
					short playerIndex = data->getShort(counter); counter+=shortSize;
					short arraySize	  = data->getShort(counter); counter+=shortSize;

					// find the correct player
					Entity* e = NULL;
					for(int i = 0; i < MAX_PLAYERS; ++i)
						if(WORLD_MODULE::Ins()->Players[i])
							if(WORLD_MODULE::Ins()->Players[i]->playerIndex == playerIndex)
								e = WORLD_MODULE::Ins()->Players[i];

					if(e)
					{
						// deserialize the item if there is one, else make sure nothing's equipped in that slot
						for(int i = 0; i < arraySize; ++i)
						{
							int exists = data->getShort(counter); counter+=shortSize;
							if(exists)
							{
								item = new Item_Equippable;
								item->deserialize(data, counter);
								e->EquipItem(i, item);
							}
							else
								e->UnequipItemM(i);

						}
					}
				}

				index += data->size();
				delete data;
				data = NULL;
				break;
			}
		case NETWORK_SEND_PROJECTILE:
			{
				ByteBuffer* data = new ByteBuffer;
				int i;
				for(i = index; i < index+size; ++i)
					data->addByte(in->getByte(i));
				i = 0;
				Item_Projectile* p = new Item_Projectile;
				p->deserialize(data, i);
				Factory::get().addMultiplayerProjectile(p);

				index += data->size();
				delete data;
				break;
			}
		case NETWORK_UPDATE_ENTITY_HEALTH:
			{
				ByteBuffer* data = new ByteBuffer;
				short id, pierce, impact, energy, i;
				for(i = index; i < index+size; ++i)
					data->addByte(in->getByte(i));
				i = 0;
				id = data->getShort(i); i += sizeof(short);
				pierce = data->getShort(i); i += sizeof(short);
				impact = data->getShort(i); i += sizeof(short);
				energy = data->getShort(i); i += sizeof(short);

				Entity* e = WORLD_MODULE::Ins()->getEntityByID(id);
				e->setDamages(pierce, impact, energy);
				break;
			}
		}

	}

	in->clear();
}